﻿using System;
using System.Collections.Generic;
using System.Text;

using FlatRedBall;
using FlatRedBall.Math.Geometry;
using FlatRedBall.Graphics;
using FlatRedBall.Graphics.Animation;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;

using ROMAP.Utilities;

namespace ROMAP.Entities
{
    public enum JumpState
    {
        None,
        Jump,
        InAir
    }
    public class Player : Entity
    {
        #region Fields
        private Sprite _sprite;
        private Sprite _pulseSprite;
        private Sprite _pulseAreaSprite;
        private AxisAlignedRectangle _collision;
        private Circle _pulseCollision;                     // Version 1: Instant pulse when releasing pulse button; 
                                                            // Version 2: Releasing pulse button releases a secondary pulse
        private Circle _finalPulseCollision;                // The secondary pulse for Version 2
        private Line vectorLine;                            

        protected string PATH;
        protected int NUM_FRAMES;
        protected float FRAME_TIME;

        private const float MAX_MS = 28.0f;                 // Maximum movespeed
        private const float MAX_PULSE_MS = 100.0f;          // Maximum movespeed from pulsing
        private const float MOVEMENT_ACCEL = 350.0f;        // Movement acceleration
        private const float AIR_DRAG = 5.0f;                // Drag from air resistance
        private const float DRAG = 120.0f;                  // Drag from ground friction                 
        private const float MAX_PULSE_RADIUS = 5.0f;        // Maximum Pulse radius
        private const float MAX_FALL_SPEED = 75.0f;        // Maximum fall speed (Prevent falling through tiles)

        public Text xVelocity;

        private JumpState _js;

        public float xstart;
        public float ystart;

        public int health;

        private bool _keyboardMovement = false;             // Helping in the determination of decceleration
        private bool _canPulse = true;                      // Whether or not the pulsing ability is active
        private bool _lookingRight = true;                  // What direction the player is facing
        private bool _movingRight = true;                   // What initial direction the player was moving before decceleration
        private bool _pulsing = false;                      // True if a pulse has been initiated, else false
        private bool _startPulseAnim = false;
        private bool _testing = false;                      // Whether or not testing levels
        private float _pulseScaleX;                         // The changing X scale of the pulse sprite
        private float _pulseScaleY;                         // The changing Y scale of the pulse sprite
        private float _ratio;                               // Ratio of the radius of the pulse collision to the maximum radius
        private int _deathCounter = 0;                      // Keep track of number of deaths

        #endregion

        #region Properties
        public AxisAlignedRectangle Collision
        {
            get { return _collision; }
        }

        public Circle PulseCollision
        {
            get { return _pulseCollision; }
        }

        public Circle FinalPulseCollision
        {
            get { return _finalPulseCollision; }
        }

        public bool Pulsing
        {
            get { return _pulsing; }
            set { _pulsing = value; }
        }

        public bool CanPulse
        {
            get { return _canPulse; }
            set { _canPulse = value; }
        }

        public JumpState JumpState
        {
            get { return _js; }
            set { _js = value; }
        }

        public bool CueRestart
        {
            get;
            set;
        }

        public bool Testing
        {
            get { return _testing; }
            set { _testing = value; }
        }

        public float PulseAreaRed
        {
            get;
            set;
        }

        public float PulseAreaBlue
        {
            get;
            set;
        }

        public override Vector2i TilePosition
        {
            get
            {
                if (_tilePosition == null)
                    _tilePosition = new Vector2i();

                _tilePosition.X = (int)Math.Round(this.X / TILE_SIZE);
                _tilePosition.Y = -(int)Math.Round(this.Y / TILE_SIZE);
                return _tilePosition;
            }
            set
            {
                _tilePosition = value;
                float realX = value.X * TILE_SIZE;
                float realY = -value.Y * TILE_SIZE;

                this.X = realX;
                this.Y = realY;
            }
        }

        #endregion

        #region Methods

        // Constructor
        public Player(string contentManagerName)
        {
            PATH = @"Content/Player/";
            NUM_FRAMES = 8;
            FRAME_TIME = 0.1f;

            Initialize(true);
        }

        protected virtual void Initialize(bool addToManagers)
        {
            // Here you can preload any content you will be using
            // like .scnx files or texture files.

            base.Initialize();

            AnimationChainList ani = new AnimationChainList();
            AnimationChain rightIdle = InitializeAnimation(PATH + "IdleRight/", NUM_FRAMES, FRAME_TIME);
            AnimationChain leftIdle = InitializeAnimation(PATH + "IdleLeft/", NUM_FRAMES, FRAME_TIME);
            AnimationChain rightRun = InitializeAnimation(PATH + "RunRight/", 10, FRAME_TIME);
            AnimationChain leftRun = InitializeAnimation(PATH + "RunLeft/", 10, FRAME_TIME);
            AnimationChain rightAir = InitializeAnimation(PATH + "AirRight/", 2, FRAME_TIME);
            AnimationChain leftAir = InitializeAnimation(PATH + "AirLeft/", 2, FRAME_TIME);
            AnimationChain rightFall = InitializeAnimation(PATH + "FallingRight/", 1, FRAME_TIME);
            AnimationChain leftFall = InitializeAnimation(PATH + "Fallingleft/", 1, FRAME_TIME);
            AnimationChain rightDeath = InitializeAnimation(PATH + "DeathRight/", 8, FRAME_TIME);
            AnimationChain leftDeath = InitializeAnimation(PATH + "DeathLeft/", 8, FRAME_TIME);

            ani.Add(rightIdle); ani.Add(leftIdle);
            ani.Add(rightRun); ani.Add(leftRun);
            ani.Add(rightAir); ani.Add(leftAir);
            ani.Add(rightFall); ani.Add(leftFall);
            ani.Add(rightDeath); ani.Add(leftDeath);

            rightIdle.Name = "rightIdle"; leftIdle.Name = "leftIdle";
            rightRun.Name = "rightRun"; leftRun.Name = "leftRun";
            rightAir.Name = "rightAir"; leftAir.Name = "leftAir";
            rightFall.Name = "rightFall"; leftFall.Name = "leftFall";
            rightDeath.Name = "rightDeath"; leftDeath.Name = "leftDeath";

            _sprite = SpriteManager.AddSprite(ani);

            _sprite.CurrentChainName = "rightIdle";

            _pulseSprite = SpriteManager.AddSprite(PATH + "/Pulse/Pulse");
            _pulseAreaSprite = SpriteManager.AddSprite(PATH + "/Pulse/PulseArea");

            if (addToManagers)
            {
                AddToManagers();
            }
        }


        public virtual void AddToManagers()
        {
            _sprite.AttachTo(this, false);
            _sprite.PixelScale();
            _sprite.ScaleX *= .25f;
            _sprite.ScaleY *= .25f;
            _sprite.RelativeZ = 1.5f;

            _pulseSprite.AttachTo(this, false);
            _pulseSprite.PixelScale();
            _pulseSprite.ScaleX *= .82f;
            _pulseSprite.ScaleY *= .82f;
            _pulseScaleX = _pulseSprite.ScaleX;
            _pulseScaleY = _pulseSprite.ScaleY;
            _pulseSprite.ScaleX = 0.0f;
            _pulseSprite.ScaleY = 0.0f;
            _pulseSprite.RelativeZ = 1.6f;

            _pulseAreaSprite.AttachTo(this, false);
            _pulseAreaSprite.PixelScale();
            _pulseAreaSprite.ScaleX = 0.0f;
            _pulseAreaSprite.ScaleY = 0.0f;
            _pulseAreaSprite.RelativeZ = 1.4f;
            _pulseAreaSprite.ColorOperation = ColorOperation.Modulate;
            _pulseAreaSprite.Red = 1.0f;
            _pulseAreaSprite.Green = 1.0f;
            _pulseAreaSprite.Blue = 1.0f;

            _collision = ShapeManager.AddAxisAlignedRectangle();
            _collision.AttachTo(this, false);
            _collision.ScaleX = _sprite.ScaleX * 0.5f;
            _collision.ScaleY = _sprite.ScaleY * 0.9f;
            _collision.Visible = false;

            _pulseCollision = ShapeManager.AddCircle();
            _pulseCollision.AttachTo(this, false);
            _pulseCollision.Radius = 0.0f;
            _pulseCollision.Visible = false;

            _finalPulseCollision = ShapeManager.AddCircle();
            _finalPulseCollision.Radius = 0.0f;
            _finalPulseCollision.AttachTo(this, false);

            vectorLine = ShapeManager.AddLine();
            vectorLine.AttachTo(this, false);
            vectorLine.RelativePoint1 = new Point3D(0, 0, 0);
            vectorLine.Visible = false;

            health = 3;

            _js = JumpState.None;

            //FOR DEBUGGING//////////////////////////////
            xVelocity = TextManager.AddText("");
            xVelocity.Scale = 1.0f;
            xVelocity.Spacing = 2.0f;
            xVelocity.Visible = false;
            xVelocity.AttachTo(SpriteManager.Camera, true);
            xVelocity.SetColor(1.0f, 1.0f, 1.0f);
            xVelocity.RelativeX = -xVelocity.ScaleX + 20;
            xVelocity.RelativeY = xVelocity.ScaleY - 14;
            /////////////////////////////////////////////
        }


        public virtual void Destroy()
        {
            base.Destroy();

            SpriteManager.RemoveSprite(_sprite);
            SpriteManager.RemoveSprite(_pulseSprite);
            SpriteManager.RemoveSprite(_pulseAreaSprite);

            ShapeManager.Remove(_collision);
            ShapeManager.Remove(_pulseCollision);
            ShapeManager.Remove(_finalPulseCollision);
            ShapeManager.Remove(vectorLine);

            TextManager.RemoveText(xVelocity);

            SpriteManager.Camera.X = 0;
            SpriteManager.Camera.Y = 0;

            base.Destroy();
        }


        public override void Activity()
        {
            SpriteManager.Camera.X = this.X;
            SpriteManager.Camera.Y = this.Y;

            if (KeyboardManager.TestButton())
                _testing = !_testing;

            if (_testing)
            {
                if (KeyboardManager.MoveUp())
                    this.YVelocity = 140;
                else if (KeyboardManager.MoveDown())
                    this.YVelocity = -140;
                else
                    this.YVelocity = 0.0f;

                this.YAcceleration = 0.0f;
            }

            #region Player Input and Movement
            if (KeyboardManager.PressBothDirections())
            {
                this.Velocity.X = 0.0f;
                this.Acceleration.X = 0.0f;
            }
            else if (KeyboardManager.MoveLeft())
            {
                this.Acceleration.X = -MOVEMENT_ACCEL;
                _lookingRight = false;

                if (this.Velocity.X < -MAX_MS)
                    this.Velocity.X = -MAX_MS;

                _keyboardMovement = true;
            }
            else if (KeyboardManager.MoveRight())
            {
                this.Acceleration.X = MOVEMENT_ACCEL;
                _lookingRight = true;

                if (this.Velocity.X > MAX_MS)
                    this.Velocity.X = MAX_MS;

                _keyboardMovement = true;
            }

            // When the movement key is released, it will set the initial direction
            // the player was moving before deccelerating in order to determine when to stop
            // applying an opposite force for slowing the player's movement down to a halt.
            else if (KeyboardManager.ReleaseMovement())
            {
                if (this.Velocity.X != 0.0f)
                {
                    if (this.Velocity.X > 0.0f)
                        _movingRight = true;
                    else
                        _movingRight = false;

                    //Applies the opposite acceleration to the movement
                    this.Acceleration.X = -(this.Velocity.X / Math.Abs(this.Velocity.X)) * MOVEMENT_ACCEL;
                }
                else
                {
                    this.Acceleration.X = 0.0f;

                    _keyboardMovement = false;
                }
            }
            else if (_keyboardMovement)
            {
                if (_movingRight && this.Velocity.X < 0.0f)
                {
                    this.Velocity.X = 0.0f;
                    this.Acceleration.X = 0.0f;

                    _keyboardMovement = false;
                }
                else if (!_movingRight && this.Velocity.X > 0.0f)
                {
                    this.Velocity.X = 0.0f;
                    this.Acceleration.X = 0.0f;

                    _keyboardMovement = false;
                }
            }

            //Slowing the player down when in the air
            if (!_keyboardMovement && this.JumpState == JumpState.InAir)
            {
                if (this.Velocity.X != 0.0f)
                {
                    //Applies the opposite acceleration to the movement
                    this.Acceleration.X = -(this.Velocity.X / Math.Abs(this.Velocity.X)) * AIR_DRAG * 0;
                }
                else
                {
                    this.Velocity.X = 0;
                    this.Acceleration.X = 0.0f;
                }
            }
            //Slowing the player down when moving on the ground
            else if (!_keyboardMovement && this.JumpState == JumpState.None)
            {
                if (this.Velocity.X != 0.0f)
                {
                    //Applies the opposite acceleration to the movement
                    this.Acceleration.X = -(this.Velocity.X / Math.Abs(this.Velocity.X)) * DRAG;
                }
                else
                {
                    this.Velocity.X = 0;
                    this.Acceleration.X = 0.0f;

                    _keyboardMovement = false;
                }
            }

            if (KeyboardManager.JumpStart())
            {
                if (_canPulse)
                    StartPulse();
            }
            else if (KeyboardManager.JumpRelease())
            {
                canPulse(false);
                EndPulse();
            }
            #endregion

            if (_pulseCollision.Radius >= MAX_PULSE_RADIUS)
            {
                _pulseCollision.RadiusVelocity = 0;
                _pulseCollision.Radius = MAX_PULSE_RADIUS;
            }

            if (_pulseAreaSprite.ScaleX >= _pulseScaleX)
            {
                _pulseAreaSprite.ScaleXVelocity = 0.0f;
                _pulseAreaSprite.ScaleYVelocity = 0.0f;
                _pulseAreaSprite.ScaleX = _pulseScaleX;
                _pulseAreaSprite.ScaleY = _pulseScaleY;
            }

            if (_startPulseAnim)
            {
                if (_pulseSprite.ScaleX > (_pulseScaleX * _ratio))
                {
                    _pulseSprite.ScaleXVelocity = 0.0f;
                    _pulseSprite.ScaleYVelocity = 0.0f;
                    _startPulseAnim = false;
                    _pulseSprite.ScaleX = 0.0f;
                    _pulseSprite.ScaleY = 0.0f;
                }
                else
                {
                    _pulseSprite.ScaleXVelocity = 40.0f;
                    _pulseSprite.ScaleYVelocity = 40.0f;
                }
            }
            else
            {
                _pulseSprite.ScaleXVelocity = 0.0f;
                _pulseSprite.ScaleYVelocity = 0.0f;
                _pulseSprite.ScaleX = 0.0f;
                _pulseSprite.ScaleY = 0.0f;
            }

            if (this.JumpState == JumpState.InAir && !_testing)
            {
                this.Acceleration.Y = GameManager.GRAVITY;
            }


            if (JumpState != JumpState.Jump)
            {
                if (!_lookingRight)
                {
                    if (this.Velocity.X > 0)
                    {
                        this.Velocity.X = 0;
                        this.Acceleration.X = 0;
                    }
                }
                else
                {
                    if (this.Velocity.X < 0)
                    {
                        this.Velocity.X = 0;
                        this.Acceleration.X = 0;
                    }
                }
            }

            //Max Velocity Check
            if (this.Velocity.X >  MAX_PULSE_MS) //if player velocity is greater than MAX_PULSE_MS
                this.Velocity.X =  MAX_PULSE_MS;
            if (this.Velocity.X < -MAX_PULSE_MS) //if player velocity is greater than MAX_PULSE_MS
                this.Velocity.X = -MAX_PULSE_MS;

            xVelocity.Visible = true;

            if (this.YVelocity > 0.0)
            {
                if (this.Velocity.X < 0)
                    _sprite.CurrentChainName = "leftAir";
                else if (this.Velocity.X > 0)
                    _sprite.CurrentChainName = "rightAir";
                else 
                {
                    if (_sprite.CurrentChainName.Contains("right"))
                        _sprite.CurrentChainName = "rightAir";
                    else
                        _sprite.CurrentChainName = "leftAir";
                }
            }
            else if (this.YVelocity < -1.67f)
            {
                if (this.Velocity.X < 0)
                    _sprite.CurrentChainName = "leftFall";
                else if (this.Velocity.X > 0)
                    _sprite.CurrentChainName = "rightFall";
                else
                {
                    if (_sprite.CurrentChainName.Contains("right"))
                        _sprite.CurrentChainName = "rightFall";
                    else
                        _sprite.CurrentChainName = "leftFall";
                }
            }
            else
            {
                if (this.Velocity.X < 0)
                    _sprite.CurrentChainName = "leftRun";
                else if (this.Velocity.X > 0)
                    _sprite.CurrentChainName = "rightRun";
                else
                {
                    if (_lookingRight)
                        _sprite.CurrentChainName = "rightIdle";
                    else
                        _sprite.CurrentChainName = "leftIdle";
                }
            }

            if (this.YVelocity <= -MAX_FALL_SPEED)
                this.YVelocity = -MAX_FALL_SPEED;

            _pulseAreaSprite.Red = PulseAreaRed;
            _pulseAreaSprite.Blue = PulseAreaBlue;

            #region Camera Zooming Test
            /*
            float zoomPercentage = Math.Abs(this.YVelocity) / (MAX_PULSE_MS);

            const int MAX_ZOOM = 45;
            const int MIN_ZOOM = 25;

            float cameraZoom = (zoomPercentage * (MAX_ZOOM - MIN_ZOOM)) + MIN_ZOOM;

            if (cameraZoom < MIN_ZOOM)
                cameraZoom = MIN_ZOOM;
            else if (cameraZoom > MAX_ZOOM)
                cameraZoom = MAX_ZOOM;

            SpriteManager.Camera.RelativeZ = cameraZoom;
             * */
            #endregion
        }

        #region Pulse Version 1
        /*
        public void Pulse(Vector3 finalVector)
        {
            Vector3 pulseVector = Vector3.Zero;

            JumpState = JumpState.Jump;

            if (finalVector.X < 0)
            {
                _sprite.CurrentChainName = "leftRun";
                _lookingRight = false;
            }
            else
            {
                _sprite.CurrentChainName = "rightRun";
                _lookingRight = true;
            }

            if (finalVector.Length() != 0)
                finalVector = finalVector / finalVector.Length();
            this.Velocity.Y += finalVector.Y * _pulseCollision.Radius * 10.0f;
            this.Velocity.X += finalVector.X * _pulseCollision.Radius * 30.0f;
            vectorLine.RelativePoint2 = new Point3D(finalVector);
            vectorLine.Visible = false;

            _canPulse = true;
            _pulseCollision.Radius = 0.0f;

            _pulsing = false;
        }*/
        #endregion

        public void ChangeDirection(float xVelocity)
        {
            if (xVelocity < 0)
            {
                _sprite.CurrentChainName = "leftRun";
                _lookingRight = false;
            }
            else
            {
                _sprite.CurrentChainName = "rightRun";
                _lookingRight = true;
            }
        }

        FlatRedBall.Math.Geometry.Point p1 = new FlatRedBall.Math.Geometry.Point(0.0f, 0.0f);
        FlatRedBall.Math.Geometry.Point p2 = new FlatRedBall.Math.Geometry.Point(0.0f, 0.0f);

        public void StartPulse()
        {
            _pulseCollision.RadiusVelocity = 20;

            _pulseAreaSprite.ScaleXVelocity = 20.0f;
            _pulseAreaSprite.ScaleYVelocity = 20.0f;
        }

        public void EndPulse()
        {

            _pulseCollision.RadiusVelocity = 0;
            _pulseAreaSprite.ScaleXVelocity = 0.0f;
            _pulseAreaSprite.ScaleYVelocity = 0.0f;
            _pulseAreaSprite.ScaleX = 0.0f;
            _pulseAreaSprite.ScaleY = 0.0f;

            _finalPulseCollision.RadiusVelocity = 50.0f;
            _ratio = _pulseCollision.Radius / MAX_PULSE_RADIUS;
            _startPulseAnim = true;
            _pulsing = true;
        }

        //Initializes the Animation Chain. Not List.
        protected AnimationChain InitializeAnimation(string path, int numFrames, float frameTime)
        {
            // Loads the animation chain
            AnimationChain chain = new AnimationChain();

            for (int i = 1; i <= numFrames; i++)
            {
                AnimationFrame frame = new AnimationFrame(path + i, frameTime, "Global");

                chain.Add(frame);
            }

            return chain;
        }

        public void canPulse(bool boolean)
        {
            _canPulse = boolean;
        }

        public void DeathAnimation()
        {
            _sprite.CurrentFrameIndex = 1;

            if (_sprite.CurrentChainName.Contains("right"))
                _sprite.CurrentChainName = "rightDeath";
            else
                _sprite.CurrentChainName = "leftDeath";

            _sprite.Animate = true;
        }

        public void CheckDeath()
        {
            _startPulseAnim = false;
            _pulseAreaSprite.Visible = false;
            _pulseSprite.Visible = false;

            if (_sprite.JustCycled)
            {
                _sprite.Visible = false;
                _sprite.Animate = false;
                _deathCounter++;
                CueRestart = true;
            }
        }

        #endregion

    }
}
